/**
 * @file: main.cpp
 * @author: Lynxi SDK Team
 * @brief
 * @version: 1.0
 * @date 2022-10-25
 *
 * Copyright:
 * © 2018 北京灵汐科技有限公司 版权所有。
 * 注意：以下内容均为北京灵汐科技有限公司原创，未经本公司允许，不得转载，否则将视为侵权；对于不遵守此声明或者其他违法使用以下内容者，本公司依法保留追究权。
 * © 2018 Lynxi Technologies Co., Ltd. All rights reserved.
 * NOTICE: All information contained here is, and remains the property of Lynxi.
 * This file can not be copied or distributed without the permission of Lynxi
 * Technologies Co., Ltd.
 */
#include "blockQueue.hpp"
#include "bufferPool.hpp"
#include "frameRater.h"
#include "nlohmann/json.hpp"
#include "util.hpp"
#include <atomic>
#include <fstream>
#include <iostream>
#include <lyn_api.h>
#include "CLI/CLI11.hpp" // 引入 CLI11 头文件

using json = nlohmann::json;

std::atomic_int g_totalSendNum(0);
std::atomic_int g_totalRecvNum(0);
std::atomic_int g_runningDecoders(0);

static std::map<uint16_t, uint16_t> g_chipChannlMap;

struct DecoderAttr
{
  std::string url;
  lynPixelFormat_t outFmt = LYN_PIX_FMT_NV12;
  lynScale_t scale = SCALE_NONE;
  std::string outputPath;
  int outnumMin = 1;
  int outnumMax = 10;
  int chanId = 0;
  uint32_t repeatNum = 1;
  uint32_t deviceId = 0;
  std::string channelName;
  lynContext_t *context;
};

struct RecvCbData
{
  lynFrame_t frame;
  std::shared_ptr<BufferPool> framePool;
  int recvNum = 0;
  DecoderAttr *attr;
  FrameRater *pFrameRater;
};

class VideoDecoder
{
public:
  void Run(DecoderAttr attr);
  ~VideoDecoder();

private:
  void SendThreadFunc();
  void RecvThreadFunc();

  DecoderAttr m_attr;
  lynContext_t m_ctx = nullptr;
  std::thread *m_sendThread;
  std::thread *m_recvThread;
  lynDemuxHandle_t m_demuxHdl = nullptr;
  lynCodecPara_t m_codecPara;
  lynVdecHandle_t m_vdecHdl = nullptr;
  lynVdecAttr_t m_vdecAttr;
  lynVdecOutInfo_t m_vdecOutInfo;
  BlockQueue<int> m_sendQueue;
  int m_sendNum = 0;
};

/**
 * @brief 解码器运行函数
 *
 * @param  attr 运行参数
 */
void VideoDecoder::Run(DecoderAttr attr)
{
  m_attr = attr;
  m_ctx = *m_attr.context;
  // 1 设置上下文环境
  CHECK_ERR(lynSetCurrentContext(m_ctx));

  // 2 打开解封装器
  CHECK_ERR(lynDemuxOpen(&m_demuxHdl, m_attr.url.c_str(), nullptr));

  // 3 获取视频信息
  CHECK_ERR(lynDemuxGetCodecPara(m_demuxHdl, &m_codecPara));

  // 4 打开解码器
  m_vdecAttr.codecId = m_codecPara.codecId;
  m_vdecAttr.outputFmt = m_attr.outFmt;
  m_vdecAttr.scale = m_attr.scale;
  CHECK_ERR(lynVdecOpen(&m_vdecHdl, &m_vdecAttr));

  // 5 获取解码输出图像大小
  CHECK_ERR(lynVdecGetOutInfo(&m_codecPara, &m_vdecAttr, &m_vdecOutInfo));

  // 6 创建发送线程
  m_sendThread = new std::thread(&VideoDecoder::SendThreadFunc, this);

  // 7 创建接收线程
  m_recvThread = new std::thread(&VideoDecoder::RecvThreadFunc, this);
}

/**
 * @brief 发送线程函数
 *
 */
void VideoDecoder::SendThreadFunc()
{
  lynPacket_t pkt;
  bool eos = false;
  int ret;
  int repeat = m_attr.repeatNum;

  // 6.1 设置上下文环境
  CHECK_ERR(lynSetCurrentContext(m_ctx));

  // 6.2 创建解码器发送指令流
  lynStream_t sendStream = nullptr;
  CHECK_ERR(lynCreateStream(&sendStream));
  do
  {
    // 6.2 从解封装器获取一个包
    ret = lynDemuxReadPacket(m_demuxHdl, &pkt);
    if (ret != 0)
    {
      repeat--;
      CHECK_ERR(lynDemuxClose(m_demuxHdl));
      if (repeat > 0)
      {
        CHECK_ERR(lynDemuxOpen(&m_demuxHdl, m_attr.url.c_str(), nullptr));
        m_sendNum = 0;
        continue;
      }
      eos = true;
    }
    pkt.eos = eos;

    // 6.3 发送解码器解码
    CHECK_ERR(lynVdecSendPacketAsync(sendStream, m_vdecHdl, &pkt));
    CHECK_ERR(lynSynchronizeStream(sendStream));

    // 6.4 释放pkt内存，并通知接收线程接收
    if (!eos)
    {
      CHECK_ERR(lynDemuxFreePacket(&pkt));
      m_sendQueue.put(++m_sendNum);
      g_totalSendNum++;
    }
    else
    {
      m_sendQueue.put(-1);
    }
  } while (!eos);

  // 6.5 销毁解码器发送指令流
  CHECK_ERR(lynSynchronizeStream(sendStream));
  CHECK_ERR(lynDestroyStream(sendStream));
}
/**
 * @brief 接收线程函数
 *
 */
void VideoDecoder::RecvThreadFunc()
{
  // 7.1 设置接收线程上下文
  CHECK_ERR(lynSetCurrentContext(m_ctx));

  // 7.2 创建解码器接收指令流
  lynStream_t recvStream = nullptr;
  CHECK_ERR(lynCreateStream(&recvStream));

  int recvNum = 0;

  // 7.3 创建解码帧内存池
  auto framePool =
      std::make_shared<BufferPool>(m_vdecOutInfo.predictBufSize, 5);

  FrameRater frameRater(m_attr.channelName);
  frameRater.SetInterval(1);

  do
  {
    // 7.4 从解码器中获取解码结果
    bool takeResult = m_sendQueue.take(recvNum, 1000); // 1s
    if (!takeResult)
    {
      continue;
    }

    auto cbData = new RecvCbData;
    cbData->frame.eos = recvNum < 0 ? true : false;
    cbData->frame.data = (uint8_t *)framePool->Pop();
    cbData->frame.size = m_vdecOutInfo.predictBufSize;
    cbData->framePool = framePool;
    cbData->recvNum = recvNum;
    cbData->attr = &m_attr;
    cbData->pFrameRater = &frameRater;
    CHECK_ERR(lynVdecRecvFrameAsync(recvStream, m_vdecHdl, &cbData->frame));

    // 7.5 在指令执行完成后，将内存归还到内存池
    lynStreamAddCallback(
        recvStream,
        [](void *data) -> int
        {
          RecvCbData *cb = (RecvCbData *)data;
          if (cb->recvNum > 0)
          {
            cb->pFrameRater->AddFrame(1);
            g_totalRecvNum++;
          }
          else
          {
            g_runningDecoders--;
          }
          if (!cb->attr->outputPath.empty() &&
              cb->recvNum >= cb->attr->outnumMin &&
              cb->recvNum <= cb->attr->outnumMax)
          {
            uint8_t *hostBuf = (uint8_t *)malloc(cb->frame.size);
            CHECK_ERR(lynMemcpy(hostBuf, cb->frame.data, cb->frame.size,
                                ServerToClient));
            cb->framePool->Push(cb->frame.data);
            char dumpPath[128];
            sprintf(dumpPath, "%sdevice%d_dump_%d_%04d.yuv",
                    cb->attr->outputPath.c_str(), cb->attr->deviceId,
                    cb->attr->chanId, cb->recvNum);
            CHECK_ERR(DumpFile(hostBuf, cb->frame.size, dumpPath));
            free(hostBuf);
          }
          else
          {
            cb->framePool->Push(cb->frame.data);
          }

          delete cb;
          return 0;
        },
        cbData);
  } while (recvNum >= 0);

  // 7.6 销毁解码器接收指令流
  CHECK_ERR(lynSynchronizeStream(recvStream));
  CHECK_ERR(lynDestroyStream(recvStream));
}

/**
 * @brief 析构函数
 *
 */
VideoDecoder::~VideoDecoder()
{
  CHECK_ERR(lynSetCurrentContext(m_ctx));
  if (m_sendThread->joinable())
  {
    m_sendThread->join();
    delete m_sendThread;
    m_sendThread = nullptr;
  }

  if (m_recvThread->joinable())
  {
    m_recvThread->join();
    delete m_recvThread;
    m_recvThread = nullptr;
  }

  if (m_vdecHdl != nullptr)
  {
    CHECK_ERR(lynVdecClose(m_vdecHdl));
  }
}

uint32_t channelNum = 1;

bool argsParser(int argc, char *argv[], DecoderAttr &attr)
{
  ArgsParse::argChipsVec.clear();

  std::string argFrameNum;
  std::string deviceIDs;
  std::string argChannelCount;

  CLI::App app;
  // 添加选项
  app.add_option("-i", attr.url, "input MP4 video file path")->default_val("../data/decode_framerate/MOT16-09.mp4");
  app.add_option("-o", attr.outputPath, "output file path")->default_val("../data/");
  app.add_option("-n", argFrameNum, "output frame num, eg: num_min,num_max")->default_val("1,10");
  app.add_option("-d", deviceIDs, "lynxi device id 0~N-1 (N is device count), eg.:0,1,2")->default_val("0");
  app.add_option("-c", channelNum, "process channel count")->default_val(1);
  app.add_option("-r", attr.repeatNum, "repeat count")->default_val(1);

  // 解析命令行参数
  CLI11_PARSE(app, argc, argv);

  sscanf(argFrameNum.c_str(), "%d,%d", &attr.outnumMin, &attr.outnumMax);

  if (ArgsParse::setChips(deviceIDs.c_str(), ',') == false)
  {
    std::cout << "setChips error" << std::endl;
    return 0;
  }

  if (attr.url.empty())
  {
    std::cout << "input file miss" << std::endl;
    return false;
  }

  if (attr.outnumMin >= attr.outnumMax || attr.outnumMin == 0 ||
      attr.outnumMax == 0)
  {
    std::cout << "args error" << std::endl;
    return false;
  }

  return true;
}

int main(int argc, char *argv[])
{
  DecoderAttr attr;
  std::vector<DecoderAttr> decoderAttrVec;
  std::vector<lynContext_t *> contextVec;

  if (!argsParser(argc, argv, attr))
  {
    return -1;
  }

  //  创建 context
  for (uint32_t i = 0; i < ArgsParse::argChipsVec.size(); ++i)
  {
    lynContext_t *context = new lynContext_t;
    CHECK_ERR(lynCreateContext(context, ArgsParse::argChipsVec[i]));
    CHECK_ERR(lynRegisterErrorHandler(StreamErrorHandler, nullptr));
    for (uint32_t j = 0; j < channelNum; ++j)
    {
      attr.context = context;
      attr.deviceId = ArgsParse::argChipsVec[i];
      attr.chanId = j;
      attr.channelName = "device" + std::to_string(attr.deviceId) +
                         +"  channel" + "_" + std::to_string(j) + attr.url;

      decoderAttrVec.emplace_back(attr);
    }
    contextVec.emplace_back(context);
    g_chipChannlMap.emplace(ArgsParse::argChipsVec[i], ArgsParse::argChannel);
  }

  std::vector<std::shared_ptr<VideoDecoder>> decoders;

  // 根据通道数创建多路解码器
  for (uint32_t i = 0; i < decoderAttrVec.size(); ++i)
  {
    DecoderAttr deAttr = decoderAttrVec[i];
    auto decoder = std::make_shared<VideoDecoder>();
    decoders.push_back(decoder);
    decoder->Run(deAttr);
  }

  decoders.clear();

  //  释放资源
  for (auto &devCtx : contextVec)
  {
    if (devCtx)
    {
      CHECK_ERR(lynDestroyContext(*devCtx));
      delete devCtx;
    }
  }

  return 0;
}
